using System;
using Rhino.Mocks;
using MbUnit.Framework;
using NEnsure.Validators;

namespace NEnsure.UnitTests
{
	[TestFixture]
	public class ManualValidatorTests
	{
		private IValidator _validator;
		private bool _firedValidationStartEvent;
		private int _firedValidatingPropertyCount;
		private int _firedValidationEndEventCount;
		private int _numberOfContainersAtStart;
		private bool _firedValidatingP;
		private bool _firedValidatedProperty;

		[SetUp]
		public void Init()
		{
			_validator = new ManualValidator();
			_firedValidationStartEvent = false;
			_firedValidatingPropertyCount = 0;
			_firedValidationEndEventCount = 0;
			_numberOfContainersAtStart = 0;
			_firedValidatingP = false;
			_firedValidatedProperty = false;
		}

		[Test]
		[ExpectedArgumentNullException]
		public void Ensure_Should_Die_If_PropertyName_Is_Nothing()
		{
			_validator.Ensure(null, string.Empty);
		}

		[Test]
		public void Ensure_Should_Return_RuleContainer_For_Property()
		{
			IRuleContainer<string> container = _validator.Ensure("PropertyName", "PropertyValue");

			Assert.IsNotNull(container);
		}

		[Test]
		public void Ensure_Should_Return_Existing_RuleContainer_If_It_Exists_For_Property()
		{
			IRuleContainer<string> container1 = _validator.Ensure("P", "P");
			IRuleContainer<string> container2 = _validator.Ensure("P", "P");

			Assert.AreEqual(container1, container2);
		}

		[Test]
		public void Ensure_Should_Return_Existing_RuleContainer_For_Property_Regardless_Of_Case_Of_Name()
		{
			IRuleContainer<string> container1 = _validator.Ensure("P", "P");
			IRuleContainer<string> container2 = _validator.Ensure("p", "P");

			Assert.AreEqual(container1, container2);
		}

		[Test]
		[ExpectedArgumentException]
		public void Ensure_Should_Die_If_Exists_Container_For_Property_But_Values_Are_Different()
		{
			_validator.Ensure("P", "P");
			_validator.Ensure("p", "S");
		}

		[Test]
		public void Should_Correctly_Show_All_RuleContainers()
		{
			_validator.Ensure("P", "P");
			_validator.Ensure("D", "D");

			Assert.AreEqual(2, _validator.RuleContainers.Length);
		}

		[Test]
		public void Reset_Should_Delete_All_Existing_Containers()
		{
			_validator.Ensure("P", "P");
			_validator.Ensure("D", "D");

			_validator.Reset();

			Assert.AreEqual(0, _validator.RuleContainers.Length);
		}

		[Test]
		public void Should_Be_Able_To_Subscribe_To_Validation_Start()
		{
			_validator.ValidationStart += checkValidationStart;

			_validator.EnsureAll();

			Assert.IsTrue(_firedValidationStartEvent);
		}

		private void checkValidationStart(object sender, ValidationEventArgs e)
		{
			_firedValidationStartEvent = true;
			_numberOfContainersAtStart = e.Containers.Length;
		}

		[Test]
		public void Should_Be_Able_To_Subscribe_To_Validation_Of_Each_Property()
		{
			_validator.Ensure("P", "P");
			_validator.Ensure("D", "D");

			_validator.ValidatingProperty += checkValidatingProperty;

			_validator.EnsureAll();

			Assert.AreEqual(2, _firedValidatingPropertyCount);
		}

		private void checkValidatingProperty(object sender, PropertyRulesEventArgs e)
		{
			_firedValidatingPropertyCount++;
		}

		[Test]
		public void Should_Be_Able_To_Subscribe_To_Validation_End()
		{
			_validator.ValidationEnd += checkValidationEnd;

			_validator.EnsureAll();

			Assert.AreEqual(1, _firedValidationEndEventCount);
		}

		private void checkValidationEnd(object sender, EventArgs e)
		{
			_firedValidationEndEventCount++;
		}

		[Test]
		public void Should_Correctly_Add_BrokenRules_Of_All_Properties()
		{
			MockRepository mocks = new MockRepository();

			IBusinessRule<string> one = mocks.StrictMock<IBusinessRule<string>>();
			IBusinessRule<string> two = mocks.StrictMock<IBusinessRule<string>>();

			SetupResult.For(one.IsBrokenBy("Property1")).Return(true);
			SetupResult.For(one.IsBrokenBy("Property2")).Return(false);
			SetupResult.For(two.IsBrokenBy("Property2")).Return(true);
			SetupResult.For(two.IsBrokenBy("Property1")).Return(false);
			SetupResult.For(one.ErrorFormat).Return("Error 1");
			SetupResult.For(two.ErrorFormat).Return("Error 2");
			SetupResult.For(one.RuleKey).Return("Rule 1");
			SetupResult.For(two.RuleKey).Return("Rule 2");

			mocks.ReplayAll();

			_validator.Ensure("Property1", "Property1").Satisfies(one, two);
			_validator.Ensure("Property2", "Property2").Satisfies(one, two);

			_validator.EnsureAll();

			Assert.AreEqual(2, _validator.BrokenRules.Count);

			BrokenRule[] brokenRulesForRule1 = _validator.BrokenRules.GetBrokenRulesForRule(one);
			BrokenRule[] brokenRulesForProperty2 = _validator.BrokenRules.GetBrokenRulesForProperty("Property2");

			Assert.AreEqual("Property1", brokenRulesForRule1[0].PropertyName);
			Assert.AreEqual(two, brokenRulesForProperty2[0].Rule);
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void Should_Die_If_Two_Properties_With_Different_Types_Are_Added()
		{
			_validator.Ensure("One", "String");
			_validator.Ensure("One", 10);
		}

		[Test]
		public void Should_See_All_Rule_Containers_In_Validator_When_Subscribing()
		{
			_validator.ValidationStart += checkValidationStart;

			_validator.Ensure("PropertyName", "P");
			_validator.Ensure("Another", 10);

			_validator.EnsureAll();

			Assert.AreEqual(2, _numberOfContainersAtStart);
		}

		[Test]
		public void Should_Be_Able_To_Stop_Validation()
		{
			_validator.ValidationStart += stopValidation;
			_validator.ValidatingProperty += checkValidatingProperty;

			_validator.Ensure("P", string.Empty);

			_validator.EnsureAll();

			Assert.AreEqual(0, _firedValidatingPropertyCount);
		}

		private void stopValidation(object sender, ValidationEventArgs e)
		{
			e.Cancel = true; 
		}

		[Test]
		public void Should_Fire_ValidationEnd_Even_If_Validation_Is_Cancelled()
		{
			_validator.ValidationStart += stopValidation;
			_validator.ValidationEnd += checkValidationEnd;

			_validator.Ensure("P", string.Empty);

			_validator.EnsureAll();

			Assert.AreEqual(1, _firedValidationEndEventCount);
		}

		[Test]
		public void Should_Be_Able_To_Subscribe_To_Validated_Property()
		{
			_validator.ValidatedProperty += checkValidatedProperty;

			_validator.Ensure("P", string.Empty);

			_validator.EnsureAll();

			Assert.IsTrue(_firedValidatedProperty);
		}

		private void checkValidatedProperty(object sender, PropertyRulesEventArgs e)
		{
			_firedValidatedProperty = true; 
		}

		[Test]
		public void Should_Be_Able_To_Stop_Validation_Of_Property()
		{
			_validator.ValidatingProperty += stopValidationOfP;
			_validator.ValidatedProperty += checkValidatedP;

			_validator.Ensure("P", string.Empty);
			_validator.Ensure("P2", 0);

			_validator.EnsureAll();			
			
			Assert.IsFalse(_firedValidatingP);
		}

		private void stopValidationOfP(object sender, PropertyRulesEventArgs e)
		{
			if (e.PropertyName == "P")
			{
				e.Cancel = true; 
			}
		}

		private void checkValidatedP(object sender, PropertyRulesEventArgs e)
		{
			if (e.PropertyName == "P")
			{
				_firedValidatingP = true;
			}
		}
	}
}
